/* Copyright Andy Mitchell 2007. No warranties */

function KeyHandlers(options) {
	options = options || {};
	
	this.eventObservers = new EventObservers(this);
	this.eventHandlers = new EventHandlers_Mutator(this);
	
	this.handlers = new Array();
	
	this.doc = options.doc || document;
	
	this.allModifiers = ["shiftKey", "altKey", "ctrlKey"];
	this.specials = {37:"LEFT", 38:"UP", 39:"RIGHT", 40:"DOWN", 13:"ENTER", 8:"BACKSPACE"};
	
	this.active(true);
}

KeyHandlers.prototype.addHandler = function(assocObj, mKey1, mKey2, key3, callback, abortOnForm, eventType){
	// Create an object to represent this handler, and add to the array.
	// (keyX in char form)
	// Activate the key listener.
	
	
	var hObj = new Object();
		hObj.mObj = new Object();
		if( mKey1 ) hObj.mObj[ mKey1 + "Key" ] = true;
		if( mKey2 ) hObj.mObj[ mKey2 + "Key" ] = true;
		hObj.key3 = key3? key3.toLowerCase() : "";
		hObj.callback = GIPROTOTYPE.bind(assocObj, callback);
		hObj.assocObj = assocObj;
		hObj.abortOnForm = abortOnForm;
		hObj.eventType = eventType;	// up|down etc.
	this.handlers.push(hObj);
};
KeyHandlers.prototype.removeHandler = function(mKey1, mKey2, key3){
	// Remove all instances of this handler.
	
	for( var i = this.handlers.length - 1; i >= 0; i-- ){
		var hObj = this.handlers[i];
		if( hObj.mKey1==mKey1 && hObj.mKey2==mKey2 && hObj.key3==key3 ){
			this.handlers.splice(i, 1);
		}
	}
};
KeyHandlers.prototype.removeHandlersByOwner = function(assocObj){
try{
	for( var i = this.handlers.length - 1; i >= 0; i-- ){
		if( this.handlers[i].assocObj==assocObj ){
			this.handlers.splice(i, 1);
		}
	}
}catch(e){
	if( GTDDEBUGMODE ) alert("removeHAndlersByOwner\n" + e.toString());
}
};

KeyHandlers.prototype.handleEventKeydown = function(event){
	// keydown records codes associated with the keyboard - up,down,enter, etc. (use keydown to get better keyboard coverage; at expense of character-coversion)
	// Override the key utilised by handleEvent
	this.eventHandlers.broadcast("keydown");
	if( this.specials[event.keyCode] ) this.handleEvent( event, this.specials[event.keyCode] );
};
KeyHandlers.prototype.handleEventKeypress = function(event){
	this.eventHandlers.broadcast("keypress");
	if( this.specials[event.keyCode] ) return;	// don't double process; or interfere with ctrl/alt
	this.handleEvent(event);
};
KeyHandlers.prototype.handleEventKeyup = function(event){
	this.eventHandlers.broadcast("keyup");
};
KeyHandlers.prototype.handleEvent = function(event, key){
	// Decide if this event matches any of our key handlers (by key's pressed), if so, broadcast to registered function.
		key = key || GIPROTOTYPE.keyChar(event);
		if( key=="" ) return;
		key = key.toLowerCase();

		var testSafe = function(el){
				var inputs = {"input":true, "textarea":true};
				return ( inputs[el.nodeName.toLowerCase()] )? false:true;
			};
	
		var lastObj = null;
		var processedObj = false;
		
		var handlers = [];
		for( var i = 0; i < this.handlers.length; i++ ) if( !this.handlers[i].disabled ) handlers.push(this.handlers[i]);	// Ensure no mutation during the process
		for( var i = 0; i < handlers.length; i++ ){
			var hObj = handlers[i];
			if( hObj ){
				if( lastObj!=hObj.assocObj ){
					processedObj = false;
					lastObj = hObj.assocObj;
				}
				if( (this.hasModifiers(event, hObj.mObj) && hObj.key3==key) || (processedObj==false && !hObj.key3 && !event.ctrlKey && !event.altKey && !this.specials[event.keyCode]) ){
					// Match
					var process = true;
					if( hObj.abortOnForm ){	// Check we're not on a form, and set to abort on forms.
						// Now check we use dodgy keys (anything without a ctrl/shift modifier)
						var safeInput = ["altKey", "ctrlKey"];
						if( !(this.hasModifier(event, safeInput)) ){
							process = testSafe( GIPROTOTYPE.element(event) );
						}
					}
					if( process ){
						GIPROTOTYPE.stop(event);
						processedObj = true;
						hObj.callback(event);
					}
				}
			}
		}
};


KeyHandlers.prototype.active = function(v){
	if( typeof v=="undefined" ) return this._active;
	
	if( v ){
		if( !this._active ){
			this._active = true;
			this.eventObservers.observe( this.doc, "keydown", this.handleEventKeydown, false );
			this.eventObservers.observe( this.doc, "keypress", this.handleEventKeypress, false );	
			this.eventObservers.observe( this.doc, "keyup", this.handleEventKeyup, false );	
		}
	}else{
		if( this._active ){
			this._active = false;
			this.eventObservers.stopObserving( this.doc, "keydown", this.handleEventKeydown, false );
			this.eventObservers.stopObserving( this.doc, "keypress", this.handleEventKeypress, false );
			this.eventObservers.stopObserving( this.doc, "keyup", this.handleEventKeyup, false );
		}
	}
};

/*
KeyHandlers.prototype.enable = function(){
	// If all keys null, do them all.
	
	if( this.isActive ) return;
	
	this.isActive = true;
	this.eventObservers.observe( this.doc, "keydown", this.handleEventKeydown, false );
	this.eventObservers.observe( this.doc, "keypress", this.handleEventKeypress, false );	
};
KeyHandlers.prototype.disable = function(){
	// If all keys null, do them all.
	
	if( !this.isActive ) return;
	
	this.eventObservers.stopObserving( this.doc, "keydown", this.handleEventKeydown, false );
	this.eventObservers.stopObserving( this.doc, "keypress", this.handleEventKeypress, false );
	this.isActive = false;
};
*/

KeyHandlers.prototype.enableByOwner = function( assocObj ){
	for( var i = 0; i < this.handlers.length; i++ ){
		if( this.handlers[i].assocObj==assocObj ) this.handlers[i].disabled = false;
	}
};
KeyHandlers.prototype.disableByOwner = function( assocObj ){
	for( var i = 0; i < this.handlers.length; i++ ){
		if( this.handlers[i].assocObj==assocObj ) this.handlers[i].disabled = true;
	}
};


KeyHandlers.prototype.getModifiersArray = function(){
	return ["ctrl", "shift", "alt"];
};

KeyHandlers.prototype.hasModifier = function(event, modifiers){
	// Return true if event has modifier active
	// modifier: shift, alt, ctrl
		
	if( !(modifiers instanceof Array) ) modifiers = [modifiers];		// Turn string to array
		
	for( var i = 0; i < modifiers.length; i++ ){
		if( event[modifiers[i]] ){
			return true;
		}	
	}
	return false;
	
};

KeyHandlers.prototype.hasModifiers = function(event, mObj){
	// Return true only if this matches exactly (including have no additional modifiers
	// Test allModifiers against both the event and the mObj; if a modifier appears in one, but not the other, fail
	
	for( var i = 0; i < this.allModifiers.length; i++ ){
		if( !(
			!event[this.allModifiers[i]] && !mObj[this.allModifiers[i]] ||
			event[this.allModifiers[i]] && mObj[this.allModifiers[i]]
			)){
			return false;
		}
	}	
	return true;
};


KeyHandlers.prototype.destroy = function(){
	this.eventObservers.destroy();
};